/*
 * Copyright (c) 2009 University of Washington
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#ifndef OBJECT_NAMES_H
#define OBJECT_NAMES_H

#include "object.h"
#include "ptr.h"

/**
 * @file
 * @ingroup config
 * Declaration of class ns3::Names.
 */

namespace ns3
{

/**
 * @ingroup config
 * @brief A directory of name and Ptr<Object> associations that allows
 * us to give any ns3 Object a name.
 */
class Names
{
  public:
    /**
     * @brief Add the association between the string "name" and the
     * Ptr<Object> obj.
     *
     * The name may begin either with "/Names" to explicitly call out
     * the fact that the name provided is installed under the root of
     * the name space, or it may begin with the name of the first object
     * in the path.  For example, Names::Add ("/Names/client", obj) and
     * Names::Add ("client", obj) accomplish exactly the same thing.  A
     * name at a given level in the name space path must be unique.  In
     * the case of the example above, it would be illegal to try and
     * associate a different object with the same name: "client" at the
     * same level ("/Names") in the path.
     *
     * As well as specifying a name at the root of the "/Names"
     * namespace, the name parameter can contain a path that fully
     * qualifies the name to be added.  For example, if you previously
     * have named an object "client" in the root namespace as above, you
     * could name an object "under" that name by making a call like
     * Names::Add ("/Names/client/eth0", obj).  This will define the
     * name "eth0" and make it reachable using the path specified.  Note
     * that Names::Add ("client/eth0", obj) would accomplish exactly the
     * same thing.
     *
     * Duplicate names are not allowed at the same level in a path,
     * however you may associate similar names with different paths.
     * For example, if you define "/Names/Client", you may not define
     * another "/Names/Client" just as you may not have two files with
     * the same name in a classical filesystem.  However, you may have
     * "/Names/Client/eth0" and "/Names/Server/eth0" defined at the same
     * time just as you might have different files of the same name
     * under different directories.
     *
     * @param [in] name The name of the object you want to associate; which may be
     *             prepended with a path to that object.
     * @param [in] object A smart pointer to the object itself.
     */
    static void Add(std::string name, Ptr<Object> object);

    /**
     * @brief An intermediate form of Names::Add allowing you to provide
     * a path to the parent object (under which you want this name to be
     * defined) in the form of a name path string.
     *
     * In some cases, it is desirable to break up the path used to
     * describe an item in the names namespace into a path and a name.
     * This is analogous to a file system operation in which you provide
     * a directory name and a file name.
     *
     * For example, consider a situation where you have previously named
     * an object "/Names/server".  If you further want to create an
     * association for between a Ptr<Object> object that you want to
     * live "under" the server in the name space -- perhaps "eth0" --
     * you could do this in two ways, depending on which was more
     * convenient: Names::Add ("/Names/server/eth0", object) or, using
     * the split path and name approach, Names::Add ("/Names/server",
     * "eth0", object).
     *
     * Duplicate names are not allowed at the same level in a path,
     * however you may associate similar names with different paths.
     * For example, if you define "/Names/Client", you may not define
     * another "/Names/Client" just as you may not have two files with
     * the same name in a classical filesystem.  However, you may have
     * "/Names/Client/eth0" and "/Names/Server/eth0" defined at the same
     * time just as you might have different files of the same name
     * under different directories.
     *
     * @param [in] path A path name describing a previously named object
     *             under which you want this new name to be defined.
     * @param [in] name The name of the object you want to associate.
     * @param [in] object A smart pointer to the object itself.
     *
     * @see Names::Add (Ptr<Object>,std::string,Ptr<Object>);
     */
    static void Add(std::string path, std::string name, Ptr<Object> object);

    /**
     * @brief A low-level form of Names::Add allowing you to specify the
     * path to the parent object (under which you want this name to be
     * defined) in the form of a previously named object.
     *
     * In some use cases, it is desirable to break up the path in the
     * names name space into a path and a name.  This is analogous to a
     * file system operation in which you provide a directory name and a
     * file name.  Recall that the path string actually refers to a
     * previously named object, "under" which you want to accomplish
     * some naming action.
     *
     * However, the path is sometimes not available, and you only have
     * the object that is represented by the path in the names name
     * space.  To support this use-case in a reasonably high-performance
     * way, the path string is can be replaced by the object pointer to
     * which that path would refer.  In the spirit of the Config code
     * where this use-case is most prominent, we refer to this object as
     * the "context" for the names operation.
     *
     * You can think of the context roughly as the inode number of a
     * directory file in Unix.  The inode number can be used to look up
     * the directory file which contains the list of file names defined
     * at that directory level.  Similarly the context is used to look
     * up an internal name service entry which contains the names
     * defined for that context.
     *
     * For example, consider a situation where you have previously named
     * an object "/Names/server".  If you further want to create an
     * association for between a Ptr<Object> object that you want to
     * live "under" the server in the name space -- perhaps "eth0" --
     * you could do this by providing a complete path to the new name:
     * Names::Add ("/Names/server/eth0", object).  If, however,
     * somewhere in your code you only had a pointer to the server, say
     * Ptr<Node> node, and not a handy path string, you could also
     * accomplish this by Names::Add (node, "eth0", object).
     *
     * Duplicate names are not allowed at the same level in a path.  In
     * the case of this method, the context object gives the same
     * information as a path string.  You may associate similar names
     * with different paths.  For example, if you define"/Names/Client",
     * you may not define another "/Names/Client" just as you may not
     * have two files with the same name in a classical filesystem.
     * However, you may have "/Names/Client/eth0" and
     * "/Names/Server/eth0" defined at the same time just as you might
     * have different files of the same name under different
     * directories.
     *
     * @param [in] context A smart pointer to an object that is used
     *             in place of the path under which you want this new
     *             name to be defined.
     * @param [in] name The name of the object you want to associate.
     * @param [in] object A smart pointer to the object itself.
     */
    static void Add(Ptr<Object> context, std::string name, Ptr<Object> object);

    /**
     * @brief Rename a previously associated name.
     *
     * The name may begin either with "/Names" to explicitly call out
     * the fact that the name provided is installed under the root of
     * the name space, or it may begin with the name of the first object
     * in the path.  For example, Names::Rename ("/Names/client",
     * "server") and Names::Rename ("client", "server") accomplish
     * exactly the same thing.  Names at a given level in the name space
     * path must be unique. In the case of the example above, it would
     * be illegal to try and rename a different object to the same name:
     * "server" at the same level ("/Names") in the path.
     *
     * As well as specifying a name at the root of the "/Names"
     * namespace, the name parameter can contain a path that fully
     * qualifies the name to be changed.  For example, if you previously
     * have (re)named an object "server" in the root namespace as above,
     * you could then rename an object "under" that name by making a
     * call like Names::Rename ("/Names/server/csma", "eth0").  This
     * will rename the object previously associated with
     * "/Names/server/csma" to "eth0" and make leave it reachable using
     * the path "/Names/server/eth0".  Note that Names::Rename
     * ("server/csma", "eth0") would accomplish exactly the same thing.
     *
     * @param [in] oldpath The current path name to the object you want
     *             to change.
     * @param [in] newname The new name of the object you want to change.
     *
     * @see Names::Add (std::string name, Ptr<Object> obj)
     */
    static void Rename(std::string oldpath, std::string newname);

    /**
     * @brief An intermediate form of Names::Rename allowing you to
     * provide a path to the parent object (under which you want this
     * name to be changed) in the form of a name path string.
     *
     * In some cases, it is desirable to break up the path used to
     * describe an item in the names namespace into a path and a name.
     * This is analogous to a file system operation in which you provide
     * a directory name and a file name.
     *
     * For example, consider a situation where you have previously named
     * an object "/Names/server/csma".  If you want to change the name
     * "csma" to "eth0", you could do this in two ways, depending on
     * which was more convenient: Names::Rename ("/Names/server/csma",
     * "eth0") or, using the split path and name approach, Names::Rename
     * ("/Names/server", "csma", "eth0").
     *
     * @param [in] path A path name describing a previously named object
     *             under which you want this name change to occur
     *             (cf. directory).
     * @param [in] oldname The currently defined name of the object.
     * @param [in] newname The new name you want the object to have.
     */
    static void Rename(std::string path, std::string oldname, std::string newname);

    /**
     * @brief A low-level form of Names::Rename allowing you to specify
     * the path to the parent object (under which you want this name to
     * be changed) in the form of a previously named object.
     *
     * In some use cases, it is desirable to break up the path in the
     * names name space into a path and a name.  This is analogous to a
     * file system operation in which you provide a directory name and a
     * file name.  Recall that the path string actually refers to a
     * previously named object, "under" which you want to accomplish
     * some naming action.
     *
     * However, the path is sometimes not available, and you only have
     * the object that is represented by the path in the names name
     * space.  To support this use-case in a reasonably high-performance
     * way, the path string is can be replaced by the object pointer to
     * which that path would refer.  In the spirit of the Config code
     * where this use-case is most prominent, we refer to this object as
     * the "context" for the names operation.
     *
     * You can think of the context roughly as the inode number of a
     * directory file in Unix.  The inode number can be used to look up
     * the directory file which contains the list of file names defined
     * at that directory level.  Similarly the context is used to look
     * up an internal name service entry which contains the names
     * defined for that context.
     *
     * For example, consider a situation where you have previously named
     * an object "/Names/server/csma".  If you later decide to rename
     * the csma object to say "eth0" -- you could do this by providing a
     * complete path as in Names::Rename ("/Names/server/csma", "eth0").
     * If, however, somewhere in your code you only had a pointer to the
     * server, and not a handy path string, say Ptr<Node> node, you
     * could also accomplish this by Names::Rename (node, "csma",
     * "eth0").
     *
     * @param [in] context A smart pointer to an object that is used
     *             in place of the path under which you want this
     *             new name to be defined.
     * @param [in] oldname The current shortname of the object you want
     *             to change.
     * @param [in] newname The new shortname of the object you want
     *             to change.
     */
    static void Rename(Ptr<Object> context, std::string oldname, std::string newname);

    /**
     * @brief Given a pointer to an object, look to see if that object
     * has a name associated with it and, if so, return the name of the
     * object otherwise return an empty string.
     *
     * An object can be referred to in two ways.  Either you can talk
     * about it using its fully qualified path name, for example,
     * "/Names/client/eth0" or you can refer to it by its name, in this
     * case "eth0".
     *
     * This method returns the name of the object, e.g., "eth0".
     *
     * @param [in] object A smart pointer to an object for which you want
     *             to find its name.
     *
     * @returns A string containing the name of the object if found,
     *          otherwise the empty string.
     */
    static std::string FindName(Ptr<Object> object);

    /**
     * @brief Given a pointer to an object, look to see if that object
     * has a name associated with it and return the fully qualified name
     * path of the object otherwise return an empty string.
     *
     * An object can be referred to in two ways.  Either you can talk
     * about it using its fully qualified path name, for example,
     * "/Names/client/eth0" or you can refer to it by its name, in this
     * case "eth0".
     *
     * This method returns the name path of the object, e.g.,
     * "Names/client/eth0".
     *
     * @param [in] object A smart pointer to an object for which you
     *             want to find its fullname.
     *
     * @returns A string containing the name path of the object,
     *          otherwise the empty string.
     */
    static std::string FindPath(Ptr<Object> object);

    /**
     * @brief Clear the list of objects associated with names.
     */

    static void Clear();

    /**
     * @brief Given a name path string, look to see if there's an object
     * in the system with that associated to it.  If there is, do a
     * GetObject on the resulting object to convert it to the requested
     * typename and return it.
     *
     * An object can be referred to in two ways.  Either you can talk
     * about it using its fully qualified path name, for example,
     * "/Names/client/eth0" or you can refer to it by its name, in this
     * case "eth0".
     *
     * This method requires that the name path of the object be
     * provided, e.g., "Names/client/eth0".
     *
     * @param [in] path A string containing a name space path used
     *             to locate the object.
     *
     * @returns A smart pointer to the named object converted to
     *          the requested type.
     */
    template <typename T>
    static Ptr<T> Find(std::string path);

    /**
     * @brief Given a path to an object and an object name, look through
     * the names defined under the path to see if there's an object
     * there with the given name.
     *
     * In some cases, it is desirable to break up the path used to
     * describe an item in the names namespace into a path and a name.
     * This is analogous to a file system operation in which you provide
     * a directory name and a file name.
     *
     * For example, consider a situation where you have previously named
     * an object "/Names/server/eth0".  If you want to discover the
     * object which you associated with this path, you could do this in
     * two ways, depending on which was more convenient: Names::Find
     * ("/Names/server/eth0") or, using the split path and name
     * approach, Names::Find ("/Names/server", "eth0").
     *
     * @param [in] path A path name describing a previously named object
     *             under which you want to look for the specified name.
     * @param [in] name A string containing a name to search for.
     *
     * @returns A smart pointer to the named object converted to
     *          the requested type.
     */
    template <typename T>
    static Ptr<T> Find(std::string path, std::string name);

    /**
     * @brief Given a path to an object and an object name, look through
     * the names defined under the path to see if there's an object
     * there with the given name.
     *
     * In some cases, it is desirable to break up the path used to
     * describe an item in the names namespace into a path and a name.
     * This is analogous to a file system operation in which you provide
     * a directory name and a file name.
     *
     * For example, consider a situation where you have previously named
     * an object "/Names/server/eth0".  If you want to discover the
     * object which you associated with this path, you could do this in
     * two ways, depending on which was more convenient: Names::Find
     * ("/Names/server/eth0") or, using the split path and name
     * approach, Names::Find ("/Names/server", "eth0").
     *
     * However, the path is sometimes not available, and you only have
     * the object that is represented by the path in the names name
     * space.  To support this use-case in a reasonably high-performance
     * way, the path string is can be replaced by the object pointer to
     * which that path would refer.  In the spirit of the Config code
     * where this use-case is most prominent, we refer to this object as
     * the "context" for the names operation.
     *
     * You can think of the context roughly as the inode number of a
     * directory file in Unix.  The inode number can be used to look up
     * the directory file which contains the list of file names defined
     * at that directory level.  Similarly the context is used to look
     * up an internal name service entry which contains the names
     * defined for that context.
     *
     * @param [in] context A smart pointer to an object that is used
     *             in place of the path under which you want this
     *             new name to be defined.
     * @param [in] name A string containing a name to search for.
     *
     * @returns A smart pointer to the named object converted to
     *          the requested type.
     */
    template <typename T>
    static Ptr<T> Find(Ptr<Object> context, std::string name);

  private:
    /**
     * @brief Non-templated internal version of Names::Find
     *
     * @param [in] path A string containing the path of the object
     *             to look for.
     *
     * @returns A smart pointer to the named object.
     */
    static Ptr<Object> FindInternal(std::string path);

    /**
     * @brief Non-templated internal version of Names::Find
     *
     * @param [in] path A string containing the path to search
     *             for the object in.
     * @param [in] name A string containing the name of the object
     *             to look for.
     *
     * @returns A smart pointer to the named object.
     */
    static Ptr<Object> FindInternal(std::string path, std::string name);

    /**
     * @brief Non-templated internal version of Names::Find
     *
     * @param [in] context A smart pointer to an object under which
     *             you want to look for the provided name.
     * @param [in] name A string containing the name to look for.
     *
     * @returns A smart pointer to the named object.
     */
    static Ptr<Object> FindInternal(Ptr<Object> context, std::string name);
};

template <typename T>
/* static */
Ptr<T>
Names::Find(std::string path)
{
    Ptr<Object> obj = FindInternal(path);
    if (obj)
    {
        return obj->GetObject<T>();
    }
    else
    {
        return nullptr;
    }
}

template <typename T>
/* static */
Ptr<T>
Names::Find(std::string path, std::string name)
{
    Ptr<Object> obj = FindInternal(path, name);
    if (obj)
    {
        return obj->GetObject<T>();
    }
    else
    {
        return nullptr;
    }
}

template <typename T>
/* static */
Ptr<T>
Names::Find(Ptr<Object> context, std::string name)
{
    Ptr<Object> obj = FindInternal(context, name);
    if (obj)
    {
        return obj->GetObject<T>();
    }
    else
    {
        return nullptr;
    }
}

} // namespace ns3

#endif /* OBJECT_NAMES_H */
